#include "preHeader.h"
#include "Player.h"
#include "PlayerMgr.h"
#include "Map/MapInstance.h"
#include "Loot/LootMgr.h"

Player::Player(OBJECT_TYPE objType, uint32 clientSN,
	GateServerSession* pGateServerSession,
	GameServerSession* pGameServerSession)
: Unit(objType)
, m_clientSN(clientSN)
, m_pGateServerSession(pGateServerSession)
, m_pGameServerSession(pGameServerSession)
, m_mapState(PlayerEnterMapState)
, m_isInitFinished(false)
, m_actionUniqueKey(0)
, m_itemUniqueKey(0)
, m_questUniqueKey(0)
, m_pItemStorage(NULL)
, m_pQuestStorage(NULL)
, m_ipcF32Values{}
, m_ipcS32Values{}
, m_ipcS64Values{}
, m_lastNewDailyTime(0)
, m_lastNewWeeklyTime(0)
, m_lastNewMonthlyTime(0)
, m_isMailExporting(false)
, m_isTeleporting(false)
, m_lastWorldInstGuid(InstGUID_NULL)
, m_pTeam(NULL)
, m_guildId(0)
, m_guildTitle(-1)
, m_objHookKey4ConvoyStatus(0)
{
	RPCActor::SetReady(true);
	m_pItemStorage = new ItemStorage(this);
	m_pQuestStorage = new QuestStorage(this);
}

Player::~Player()
{
	delete m_pItemStorage;
	delete m_pQuestStorage;
}

void Player::SubInitLuaEnv()
{
	m_blackboard.SetActor(m_pMapInstance->L, this);
	this->binder::setfinal();
}

bool Player::SubInit()
{
	m_hpMaxReachable = m_ipcInfo.ipcCurHP;
	m_mpMaxReachable = m_ipcInfo.ipcCurMP;
	if (GetS64Value(UNIT64_FIELD_HP) <= 0) {
		m_isDead = true;
	}
	return true;
}

void Player::SubUpdate(uint64 diffTime)
{
	m_pQuestStorage->Update();

	UpdateTarget();
}

const std::string& Player::GetName() const
{
	return m_ipcInfo.ipcNickName;
}

void Player::SetMapViewingDistance(MapInstance *pMapInstance)
{
	auto viewingDistance = pMapInstance->getMapInfo()->viewing_distance;
	if (viewingDistance > .0f) {
		AoiActor::SetRadius(viewingDistance);
		return;
	}
	auto& viewingDistances = sGameConfig.MapTypeViewingDistances;
	auto itr = viewingDistances.find(pMapInstance->getMapType());
	if (itr != viewingDistances.end()) {
		AoiActor::SetRadius(itr->second);
		return;
	}
	AoiActor::SetRadius(36.f);
}

bool Player::AddToWorld(MapInstance *pMapInstance)
{
	SetMapViewingDistance(pMapInstance);
	if (!PushToWorld(pMapInstance)) {
		return false;
	}
	sPlayerMgr.AddPlayer(GetGuid(), pMapInstance->getInstGuid());
	return true;
}

void Player::OnPrePushToWorld()
{
	NetPacket pack(SMSG_PLAYER_ENTER_MAP);
	pack << GetGuid() << GetInstGuid();
	pack << GetPosition() << GetDirection();
	SendPacket(pack);

	if (m_pMapInstance->getMapInfo()->flags.isRecoveryHPDisable) {
		m_isRecoveryHPDisable = true;
	}

	Unit::OnPrePushToWorld();
}

void Player::OnPushToWorld()
{
	switch (m_mapState) {
	case PlayerEnterMapState:
		OnFirstPushToWorld();
		break;
	case PlayerStrayMapState:
		break;
	default:
		WLOG("Map[%hu,%hu,%u]: OnPushToWorld(%hu,%u) abnormal ...",
			m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
			m_guid.SID, m_guid.UID);
		break;
	}

	m_mapState = PlayerInMapState;
	m_isInitFinished = true;
	TryInitNewPlayer();
	OnFinishInMapState();

	Unit::OnPushToWorld();
}

void Player::OnPreLeaveWorld()
{
	SaveCharacterAsync();
	Unit::OnPreLeaveWorld();
}

void Player::OnLeftWorld()
{
	FlushPacket();
	Unit::OnLeftWorld();
}

void Player::OnDelete()
{
	sPlayerMgr.RemovePlayer(GetGuid());
	m_pMapInstance->RemoveTeamMember(this);
	ObjectHookEvent_OnPlayerDelete();
	Unit::OnDelete();
}

bool Player::IsDeletable()
{
	if (AsyncTaskOwner::HasTask() || AsyncTaskOwner::HasSubject()) {
		return false;
	}
	return Unit::IsDeletable();
}

void Player::BuildCreatePacketForPlayer(INetPacket& pck, Player* pPlayer)
{
	Unit::BuildCreatePacketForPlayer(pck, pPlayer);
	pck << m_ipcInfo.ipcNickName;
	if (this == pPlayer) {
		m_pItemStorage->BuildCreatePacketForPlayer(pck, pPlayer);
		m_pQuestStorage->BuildCreatePacketForPlayer(pck, pPlayer);
	}
}

void Player::SendPackets(const INetPacket *pcks[], size_t pck_num,
	const std::string_view datas[], size_t data_num)
{
	if (!IsType(TYPE_PLAYER)) {
		return;
	}

	assert(pck_num >= 1);
	auto packet_total_size = pcks[0]->GetReadableSize();
	for (size_t i = 1; i < pck_num; ++i) {
		packet_total_size +=
			pcks[i]->GetReadableSize() + INetPacket::Header::SIZE;
	}
	for (size_t i = 0; i < data_num; ++i) {
		packet_total_size += datas[i].size();
	}

	if (m_sendPacketBuffer.GetTotalSize() + packet_total_size +
		INetPacket::Header::SIZE > m_sendPacketBuffer.GetBufferSize())
	{
		FlushPacket();
	}

	if (packet_total_size + (INetPacket::Header::SIZE << 1) >
		m_sendPacketBuffer.GetBufferSize())
	{
		m_pGateServerSession->TransPacket(
			m_clientSN, pcks, pck_num, datas, data_num);
		return;
	}

	for (size_t i = 0; i < pck_num; ++i) {
		INetPacket::Header header(pcks[i]->GetOpcode(),
			packet_total_size + INetPacket::Header::SIZE);
		m_sendPacketBuffer.WriteHeader(header);
		m_sendPacketBuffer.Append(pcks[i]->GetReadableBuffer(),
			pcks[i]->GetReadableSize());
		packet_total_size -=
			pcks[i]->GetReadableSize() + INetPacket::Header::SIZE;
	}
	for (size_t i = 0; i < data_num; ++i) {
		m_sendPacketBuffer.Append(datas[i].data(), datas[i].size());
	}
}

void Player::SendPacket(const INetPacket& pck)
{
	if (!IsType(TYPE_PLAYER)) {
		return;
	}

	if (m_sendPacketBuffer.GetTotalSize() + pck.GetReadableSize() +
		INetPacket::Header::SIZE > m_sendPacketBuffer.GetBufferSize())
	{
		FlushPacket();
	}

	if (pck.GetReadableSize() + (INetPacket::Header::SIZE << 1) >
		m_sendPacketBuffer.GetBufferSize())
	{
		m_pGateServerSession->TransPacket(m_clientSN, pck);
		return;
	}

	m_sendPacketBuffer.WritePacket(pck);
}

void Player::SendPacket2Gs(const INetPacket& pck)
{
	if (IsType(TYPE_PLAYER)) {
		m_pGameServerSession->PushSendPacket(pck);
	}	
}

void Player::SendError(GErrorCode error)
{
	NetPacket pack(SMSG_ERROR);
	pack << error;
	SendPacket(pack);
}

void Player::SendError(const GErrorCodeP1& error)
{
	NetPacket pack(SMSG_ERROR);
	pack << error.errCode << error.p1;
	SendPacket(pack);
}

void Player::SendError(const GErrorCodeP2& error)
{
	NetPacket pack(SMSG_ERROR);
	pack << error.errCode << error.p1 << error.p2;
	SendPacket(pack);
}

void Player::SendError(const GErrorCodeP3& error)
{
	NetPacket pack(SMSG_ERROR);
	pack << error.errCode << error.p1 << error.p2 << error.p3;
	SendPacket(pack);
}

void Player::SendError(const GErrorInfo& error)
{
	NetPacket pack(SMSG_ERROR);
	pack << error.errCode;
	pack.Append(error.errArgs.data(), error.errArgs.size());
	SendPacket(pack);
}

void Player::Kick(GErrorCode error)
{
	NetPacket pack(MS_KICK);
	pack << GetGuid4Gs() << (s32)error;
	SendPacket2Gs(pack);
}

void Player::FlushPacket()
{
	if (IsType(TYPE_PLAYER) && !m_sendPacketBuffer.IsBufferEmpty()) {
		m_pGateServerSession->TransPacket(
			m_clientSN, m_sendPacketBuffer.CastBufferStringView());
		m_sendPacketBuffer.Clear();
	}
}

void Player::RPCInvoke2Ss(const INetPacket &pck,
	const std::function<void(INetStream&, int32, bool)> &cb, time_t timeout)
{
	if (IsType(TYPE_PLAYER)) {
		m_pGameServerSession->RPCInvoke2SS(pck, cb, this, timeout);
	}
}

void Player::RPCReply2Ss(const INetPacket &pck, uint64 sn, int32 err, bool eof)
{
	if (IsType(TYPE_PLAYER)) {
		m_pGameServerSession->RPCReply2SS(pck, sn, err, eof);
	}
}

void Player::RPCInvoke2Gs(const INetPacket &pck,
	const std::function<void(INetStream&, int32, bool)> &cb, time_t timeout)
{
	if (IsType(TYPE_PLAYER)) {
		m_pGameServerSession->RPCInvoke(pck, cb, this, timeout);
	}
}

void Player::RPCReply2Gs(const INetPacket &pck, uint64 sn, int32 err, bool eof)
{
	if (IsType(TYPE_PLAYER)) {
		m_pGameServerSession->RPCReply(pck, sn, err, eof);
	}
}

void Player::RPCInvoke(const INetPacket &pck,
	const std::function<void(INetStream&, int32, bool)> &cb, time_t timeout)
{
	RPCActor::RPCInvoke(pck, cb, this, timeout);
}

void Player::PushRPCPacket(const INetPacket &trans,
	const INetPacket &pck, const std::string_view &args)
{
	const size_t i = trans.IsOpNil() ? 1 : 0;
	const INetPacket *pcks[] = { &trans, &pck };
	const std::string_view datas[] = { args };
	SendPackets(pcks + i, ARRAY_SIZE(pcks) - i, datas, ARRAY_SIZE(datas));
}

RPCManager* Player::GetRPCManager()
{
	return &m_pMapInstance->sRPCManager;
}

void Player::GainCheque(ChequeType type, uint64 value,
	CHEQUE_FLOW_TYPE flowType, params<uint32> flowParams)
{
	DBGASSERT(value <= INT64_MAX);
	if (value == 0) {
		return;
	}
	switch (type) {
	case ChequeType::Gold:
		GainMoney(CurrencyType::Gold, value, flowType, flowParams);
		break;
	case ChequeType::Diamond:
		GainMoney(CurrencyType::Diamond, value, flowType, flowParams);
		break;
	case ChequeType::Exp:
		GainExp(value, flowType, flowParams);
		break;
	default:
		WLOG("Invalid gain cheque %u.", type);
		break;
	}
}

void Player::CostCheque(ChequeType type, uint64 value,
	CHEQUE_FLOW_TYPE flowType, params<uint32> flowParams)
{
	DBGASSERT(value <= INT64_MAX);
	if (value == 0) {
		return;
	}
	switch (type) {
	case ChequeType::Gold:
		CostMoney(CurrencyType::Gold, value, flowType, flowParams);
		break;
	case ChequeType::Diamond:
		CostMoney(CurrencyType::Diamond, value, flowType, flowParams);
		break;
	default:
		WLOG("Invalid cost cheque %u.", type);
		break;
	}
}

int64 Player::GetCheque(ChequeType type) const
{
	switch (type) {
	case ChequeType::Gold:
		return GetMoney(CurrencyType::Gold);
	case ChequeType::Diamond:
		return GetMoney(CurrencyType::Diamond);
	default:
		WLOG("Invalid get cheque %u.", type);
		return 0;
	}
}

bool Player::IsChequeEnough(ChequeType type, uint64 value) const
{
	switch (type) {
	case ChequeType::Gold:
		return IsMoneyEnough(CurrencyType::Gold, value);
	case ChequeType::Diamond:
		return IsMoneyEnough(CurrencyType::Diamond, value);
	default:
		WLOG("Invalid enough cheque %u.", type);
		return false;
	}
}

uint64 Player::GainExp(uint64 value,
	CHEQUE_FLOW_TYPE flowType, params<uint32> flowParams)
{
	DBGASSERT(value <= INT64_MAX);
	if (value == 0) {
		return 0;
	}

	uint32 maxLevel = sGameConfig.MaxPlayerLevel;
	uint64 expValue = GetS64Value(PLAYER64_FIELD_EXP) + value;
	while (expValue >= (u64)GetS64Value(PLAYER64_FIELD_LVUP_EXP)) {
		if (maxLevel > GetLevel()) {
			expValue -= GetS64Value(PLAYER64_FIELD_LVUP_EXP);
			LevelUp();
		} else {
			break;
		}
	}

	SetS64Value(PLAYER64_FIELD_EXP,
		std::min<u64>(expValue, GetS64Value(PLAYER64_FIELD_LVUP_EXP)));

	return value;
}

void Player::LevelUp()
{
	ModS32Value(UNIT_FIELD_LEVEL, 1);
	m_ipcInfo.ipcRankValue.lastLevelTime = GET_UNIX_TIME;

	SetS64Value(PLAYER64_FIELD_LVUP_EXP, GetLevelUpExp());
	SetS64Value(PLAYER64_FIELD_EXP, 0);

	m_pQuestStorage->SetLevelDirty();
}

uint64 Player::GetLevelUpExp() const
{
	return GetDBEntry<PlayerBase>(GetLevel())->lvUpExp;
}

GErrorCode Player::GainMoney(CurrencyType type, uint64 value,
	CHEQUE_FLOW_TYPE flowType, params<uint32> flowParams)
{
	DBGASSERT(value <= INT64_MAX);
	if (value == 0) {
		return CommonSuccess;
	}
	if ((int)type < 0 || type >= CurrencyType::Max) {
		return InvalidRequest;
	}

	ModMoney(type, value, flowType, flowParams);

	return CommonSuccess;
}

GErrorCode Player::CostMoney(CurrencyType type, uint64 value,
	CHEQUE_FLOW_TYPE flowType, params<uint32> flowParams)
{
	DBGASSERT(value <= INT64_MAX);
	if (value == 0) {
		return CommonSuccess;
	}
	if ((int)type < 0 || type >= CurrencyType::Max) {
		return InvalidRequest;
	}

	auto money = std::min(GetMoney(type), sig(value));
	ModMoney(type, -money, flowType, flowParams);

	if (money < sig(value)) {
		return ErrMoneyNotEnough;
	} else {
		return CommonSuccess;
	}
}

void Player::ModMoney(CurrencyType type, int64 value,
	CHEQUE_FLOW_TYPE flowType, params<uint32> flowParams)
{
	if (value == 0) {
		return;
	}
	if ((int)type < 0 || type >= CurrencyType::Max) {
		return;
	}

	ModS64Value(S64CURRENCY(type), value);

	switch (type) {
	case CurrencyType::Gold:
		m_pQuestStorage->SetChequeValueDirty((u32)ChequeType::Gold);
		m_pQuestStorage->OnHaveCheque(
			ChequeType::Gold, value, flowType, flowParams);
		break;
	case CurrencyType::Diamond:
		m_pQuestStorage->SetChequeValueDirty((u32)ChequeType::Diamond);
		m_pQuestStorage->OnHaveCheque(
			ChequeType::Diamond, value, flowType, flowParams);
		break;
	}
}

int64 Player::GetMoney(CurrencyType type) const
{
	if ((int)type >= 0 && type < CurrencyType::Max) {
		return GetS64Value(S64CURRENCY(type));
	} else {
		return 0;
	}
}

bool Player::IsMoneyEnough(CurrencyType type, uint64 value) const
{
	DBGASSERT(value <= INT64_MAX);
	return GetMoney(type) >= sig(value);
}

GErrorCode Player::TryRevive()
{
	if (!IsDead() || IsTeleporting()) {
		return InvalidRequest;
	}
	auto errCode = TryReviveByMapGraveyard();
	if (errCode != CommonNotFound) {
		return errCode;
	}
	errCode = TryReviveByPopMapInstance();
	if (errCode != CommonNotFound) {
		return errCode;
	}
	return CommonNotImplemented;
}

GErrorCode Player::TryReviveByMapGraveyard()
{
	const MapGraveyard* pMapGraveyard = GetNearestMapGraveyard();
	if (pMapGraveyard != NULL) {
		auto tgtPos = vector3f1f(pMapGraveyard->x,
			pMapGraveyard->y, pMapGraveyard->z, pMapGraveyard->o);
		return TeleportTo(GetInstGuid(),
			tgtPos, TeleportType::SwitchMap, (uint32)TeleportFlag::Revive);
	} else {
		return CommonNotFound;
	}
}

GErrorCode Player::TryReviveByPopMapInstance()
{
	auto pMapInfo = m_pMapInstance->getMapInfo();
	if (pMapInfo->pop_map_id != 0) {
		auto instGuid = MakeInstGuid(
			(uint16)MapInfo::Type::WorldMap, pMapInfo->pop_map_id);
		auto tgtPos = vector3f1f(pMapInfo->pop_pos_x,
			pMapInfo->pop_pos_y, pMapInfo->pop_pos_z, pMapInfo->pop_o);
		return TeleportTo(instGuid,
			tgtPos, TeleportType::SwitchMap, (uint32)TeleportFlag::Revive);
	} else {
		return CommonNotFound;
	}
}

const MapGraveyard* Player::GetNearestMapGraveyard() const
{
	float fMapGraveyardDist = 0;
	const MapGraveyard* pMapGraveyard = NULL;
	const std::vector<const MapGraveyard*>& mapGraveyards =
		m_pMapInstance->getGameMap().GetMapGraveyards(GetMapType());
	for (auto& pCurMapGraveyard : mapGraveyards) {
		auto fCurMapGraveyardDist = GetDistance2DSq(vector3f{
			pCurMapGraveyard->x, pCurMapGraveyard->y, pCurMapGraveyard->z});
		if (pMapGraveyard != NULL && fMapGraveyardDist <= fCurMapGraveyardDist) {
			continue;
		}
		pMapGraveyard = pCurMapGraveyard;
		fMapGraveyardDist = fCurMapGraveyardDist;
	}
	return pMapGraveyard;
}

void Player::UpdateTarget()
{
	CleanEnemyList();
	CleanEnemySenseList();
	if (CanOutCombatStatus()) {
		SetInCombat(false);
	}
}

bool Player::CanOutCombatStatus() const
{
	if (!IsInCombat()) {
		return false;
	}
	if (m_pTarget != NULL) {
		return false;
	}
	if (!m_enemyList.empty()) {
		return false;
	}
	return true;
}

void Player::CleanEnemyList()
{
	if (m_pTarget != NULL) {
		if (!IsEnemyAvail(m_pTarget, true)) {
			m_enemyList.erase(m_pTarget->GetGuid());
			SetTarget(NULL);
		}
	}

	auto itr = m_enemyList.begin();
	while (itr != m_enemyList.end()) {
		auto pEnemy = m_pMapInstance->GetUnit(itr->first);
		if (!IsEnemyAvail(pEnemy, false)) {
			itr = m_enemyList.erase(itr);
		} else {
			++itr;
		}
	}
}

bool Player::IsEnemyAvail(Unit* pEnemy, bool isCheckAttack)
{
	if (pEnemy == NULL || pEnemy->IsDead()) {
		return false;
	}
	if (pEnemy->IsType(TYPE_PLAYER) &&
		m_lastCombatTime + 10 < GET_UNIX_TIME) {
		return false;
	}
	if (isCheckAttack && !CanAttack(pEnemy)) {
		return false;
	}
	return true;
}

void Player::OnChangeCombatStatus()
{
	NetPacket pack(SMSG_COMBAT_STATUS);
	pack << IsInCombat();
	SendPacket(pack);

	Unit::OnChangeCombatStatus();
}

bool Player::IsHostile(const Unit* pTarget) const
{
	if (pTarget == this || pTarget == NULL) {
		return false;
	}

	// case player
	if (pTarget->IsKindOf(TYPE_PLAYER)) {
		auto pTargetPlayer = static_cast<const Player*>(pTarget);
		if (HasPvpFlag(PVP_FLAG_ENABLE_ZONE_SAFE) ||
			pTargetPlayer->HasPvpFlag(PVP_FLAG_ENABLE_ZONE_SAFE))
		{
			return false;
		}
		if (HasPvpFlag(PVP_FLAG_ENABLE_PVP) ||
			pTargetPlayer->HasPvpFlag(PVP_FLAG_ENABLE_PVP))
		{
			return true;
		}
		if (GetTeamSide() != ArenaTeamSide::teamInvalid &&
			pTargetPlayer->GetTeamSide() != ArenaTeamSide::teamInvalid) {
			return GetTeamSide() != pTargetPlayer->GetTeamSide() &&
				m_pMapInstance->IsFightingStage();
		}
		return false;
	}
	// case creature
	else if (pTarget->IsType(TYPE_CREATURE)) {
		auto pTargetCreature = static_cast<const Creature*>(pTarget);
		auto pTargetProto = pTargetCreature->GetProto();
		if (!pTargetProto->charFlags.isJoinCombat) {
			return false;
		}
		if (pTargetProto->charRace == (u32)CharRaceType::HostileForces) {
			return true;
		}
		if (pTargetProto->charRace == (u32)CharRaceType::FriendlyForces) {
			return false;
		}
		if (GetTeamSide() != ArenaTeamSide::teamInvalid &&
			pTargetCreature->GetTeamSide() != ArenaTeamSide::teamInvalid) {
			return GetTeamSide() != pTargetCreature->GetTeamSide() &&
				m_pMapInstance->IsFightingStage();
		}
		return false;
	}

	return false;
}

void Player::OnChangePriorZone(const MapZone* pOldZone)
{
	ChangePvpFlags();
	Unit::OnChangePriorZone(pOldZone);
}

void Player::ChangePvpFlags()
{
	const uint32 flags = GetPriorZoneFlags();

	if (flags & PVP_FLAG_ENABLE_PVP) {
		SetPvpFlag(PVP_FLAG_ENABLE_PVP);
	} else {
		RemovePvpFlag(PVP_FLAG_ENABLE_PVP);
	}

	if (flags & PVP_FLAG_ENABLE_ZONE_SAFE) {
		SetPvpFlag(PVP_FLAG_ENABLE_ZONE_SAFE);
	} else {
		RemovePvpFlag(PVP_FLAG_ENABLE_ZONE_SAFE);
	}
}

void Player::SetPvpFlag(PVP_FLAG flag)
{
	SetFlag(PLAYER_FIELD_PVP_FLAGS, flag);
}

void Player::RemovePvpFlag(PVP_FLAG flag)
{
	RemoveFlag(PLAYER_FIELD_PVP_FLAGS, flag);
}

bool Player::HasPvpFlag(PVP_FLAG flags) const
{
	return HasOneOfFlag(PLAYER_FIELD_PVP_FLAGS, flags);
}

bool Player::IsInGuideArea(uint32 lmID, float range) const
{
	auto pGuide = GetDBEntry<LandmarkPoint>(lmID);
	if (pGuide == NULL) {
		return false;
	}
	if (GetMapId() != pGuide->map_id) {
		return false;
	}
	if (GetMapType() != pGuide->map_type) {
		return false;
	}
	if (GetDistanceSq({pGuide->x, pGuide->y, pGuide->z}) > SQ(range)) {
		return false;
	}
	return true;
}

GErrorCode Player::CanEnterMap(uint32 mapId, uint16 mapType) const
{
	const MapInfo* pMapInfo = GetDBEntry<MapInfo>(mapId);
	if (pMapInfo == NULL) {
		return CommonInternalError;
	}
	return CommonSuccess;
}

GErrorCode Player::TeleportBy(uint32 tpId)
{
	auto pTP = GetDBEntry<TeleportPoint>(tpId);
	if (pTP == NULL) {
		return CommonInternalError;
	}
	return TeleportTo(MakeInstGuid(pTP->map_type, pTP->map_id),
		{pTP->x, pTP->y, pTP->z, pTP->o});
}

GErrorCode Player::TeleportTo(InstGUID instGuid, const vector3f1f& tgtPos,
	TeleportType tpType, uint32 tpFlags, const std::string_view& tpArgs)
{
	if (!IsInWorld() || IsTeleporting()) {
		return InvalidRequest;
	}

	if (GetInstGuid() == instGuid) {
		NetPacket pack(SMSG_LOC_TELEPORT);
		pack << tgtPos;
		SendPacket(pack);

		m_mapState = PlayerStrayMapState;
		m_pMapInstance->AddStrayPlayer(this);

		OutOfWorld(m_pMapInstance);
		m_pMapInstance->AddEvent(std::bind(
			&MapInstance::EventPushStrayPlayer, m_pMapInstance,
			this, tgtPos, tpFlags));
	}
	else {
		auto errCode = CanEnterMap(instGuid.MAPID, instGuid.TID);
		if (errCode != CommonSuccess) {
			return errCode;
		}

		if (m_pMapInstance->IsWorldMapInstance()) {
			m_lastWorldInstGuid = GetInstGuid();
			m_lastWorldPos = {GetPosition(), GetOrientation()};
		}

		m_mapState = PlayerLeaveMapState;

		m_pMapInstance->TeleportPlayer(
			this, instGuid, tgtPos, tpType, tpFlags, tpArgs);
	}

	TeleportBegin();
	return CommonSuccess;
}

void Player::ApplyTeleportFlagsOutOfWorld(uint32 tpFlags)
{
	if (tpFlags & (uint32)TeleportFlag::Revive) {
		FastRevive();
	}
}

void Player::ApplyTeleportFlagsInWorld(uint32 tpFlags)
{
}

void Player::TeleportBegin()
{
	m_isTeleporting = true;
}

void Player::TeleportEnd(bool isSucc)
{
	m_isTeleporting = false;
	if (!isSucc) {
		m_mapState = PlayerInMapState;
	}
}

void Player::SetTeam(MapTeam* pTeam)
{
	DBGASSERT(m_pTeam == pTeam || m_pTeam == NULL || pTeam == NULL);
	if (m_pTeam != pTeam) {
		if (m_pTeam != NULL) {
			m_pTeam->RemovePlayer(this);
		}
		if (pTeam != NULL) {
			pTeam->AddPlayer(this);
		}
		m_pTeam = pTeam;
	}
}

bool Player::IsTeamMember(ObjGUID guid) const
{
	if (m_pTeam != NULL) {
		return m_pTeam->HasMember(guid);
	} else {
		return GetGuid() == guid;
	}
}

bool Player::IsPlayTeamMember(ObjGUID guid) const
{
	switch (MapInfo::Type(GetMapType())) {
	case MapInfo::Type::Dungeon:
		return IS_KIND_OF(guid.TID, TYPE_PLAYER);
	default:
		return IsTeamMember(guid);
	}
}

GErrorCode Player::HandleTryGuildCreate()
{
	if (GetLevel() < 20) {
		return ErrLevelNotEnough;
	}
	if (!IsMoneyEnough(CurrencyType::Gold, 100)) {
		return ErrMoneyNotEnough;
	}
	return CommonSuccess;
}

GErrorCode Player::HandleGuildCreateResp()
{
	CostMoney(CurrencyType::Gold, 100, CFT_CREATE_GUILD, {});
	return CommonSuccess;
}

GErrorCode Player::CanLootPrizes2ItemStorage(uint32 lootSetID, bool canMergeable)
{
	auto itr = m_lootPrizes.find(lootSetID);
	if (itr == m_lootPrizes.end()) {
		itr = m_lootPrizes.emplace(lootSetID,
			sLootMgr.GeneratePrizes(this, lootSetID)).first;
	}
	const LootPrizes& prizes = itr->second;
	auto lackSlotCnt = sLootMgr.GetFillItemsLackSlotCount4LootItems(
		this, prizes.lootItems, canMergeable);
	if (lackSlotCnt > 0) {
		return ErrItemStorageSlotNotEnough;
	}
	return CommonSuccess;
}

void Player::LootPrizes2ItemStorage(uint32 lootSetID,
	ITEM_FLOW_TYPE itemFlowType, params<uint32> itemFlowParams,
	CHEQUE_FLOW_TYPE chequeFlowType, params<uint32> chequeFlowParams,
	bool isSendPopMsg)
{
	auto itr = m_lootPrizes.find(lootSetID);
	if (itr == m_lootPrizes.end()) {
		itr = m_lootPrizes.emplace(lootSetID,
			sLootMgr.GeneratePrizes(this, lootSetID)).first;
	}
	const LootPrizes& prizes = itr->second;
	OfferLootPrizes(prizes, itemFlowType, itemFlowParams,
		chequeFlowType, chequeFlowParams, isSendPopMsg);
	m_lootPrizes.erase(itr);
}

GErrorCode Player::TryOfferLootPrizes(const LootPrizes& prizes,
	ITEM_FLOW_TYPE itemFlowType, params<uint32> itemFlowParams,
	CHEQUE_FLOW_TYPE chequeFlowType, params<uint32> chequeFlowParams,
	bool canMergeable, bool isSendPopMsg)
{
	auto lackSlotCnt = sLootMgr.GetFillItemsLackSlotCount4LootItems(
		this, prizes.lootItems, canMergeable);
	if (lackSlotCnt > 0) {
		return ErrItemStorageSlotNotEnough;
	}
	OfferLootPrizes(prizes, itemFlowType, itemFlowParams,
		chequeFlowType, chequeFlowParams, isSendPopMsg);
	return CommonSuccess;
}

void Player::OfferLootPrizes(const LootPrizes& prizes,
	ITEM_FLOW_TYPE itemFlowType, params<uint32> itemFlowParams,
	CHEQUE_FLOW_TYPE chequeFlowType, params<uint32> chequeFlowParams,
	bool isSendPopMsg)
{
	sLootMgr.CreateAddItems4LootItems(this,
		prizes.lootItems, itemFlowType, itemFlowParams, isSendPopMsg);
	for (auto& lootCheque : prizes.lootCheques) {
		GainCheque((ChequeType)lootCheque.chequeType,
			lootCheque.chequeValue, chequeFlowType, chequeFlowParams);
	}
}

void Player::ApplyQuestVisibleCreature(QuestLog* pQuestLog, QuestWhenType type)
{
	bool isDirty = false;
	auto pTable = sDBMgr.GetTable<QuestCreatureVisible>();
	for (auto& cfg : MakeIteratorRange(pTable->VBegin(), pTable->VEnd())) {
		if (cfg.questTypeID == pQuestLog->GetQuestTypeID() &&
			cfg.questWhenType == (int8)type) {
			for (auto spawnID : cfg.spawnIDs) {
				if (cfg.isVisible) {
					m_invisibleCreatures.erase(spawnID);
				} else {
					m_invisibleCreatures.insert(spawnID);
				}
			}
			isDirty = true;
		}
	}
	if (isDirty) {
		m_pMapInstance->ReloadAoiActorSubjectOrder(this);
	}
}

bool Player::IsInvisibleCreature(uint32 spawnID) const
{
	return m_invisibleCreatures.count(spawnID) != 0;
}

std::string Player::SaveInvisibleCreatures() const
{
	TextPacker packer;
	for (auto spawnID : m_invisibleCreatures) {
		packer << spawnID;
	}
	return packer.str();
}

void Player::LoadInvisibleCreatures(const char* data)
{
	TextUnpacker unpacker(data);
	while (!unpacker.IsEmpty()) {
		m_invisibleCreatures.insert(unpacker.Unpack<uint32>());
	}
}

void Player::SaveConvoyStatus(uint32 questTypeID, std::string status)
{
	m_allConvoyStatus[questTypeID] = ConvoyStatus{m_instGuid, std::move(status)};
	TryCreateObjectHook4ConvoyStatus();
}

void Player::RemoveConvoyStatus(uint32 questTypeID)
{
	m_allConvoyStatus.erase(questTypeID);
	if (m_allConvoyStatus.empty()) {
		DetachObjectHookInfo(m_objHookKey4ConvoyStatus);
		m_objHookKey4ConvoyStatus = 0;
	}
}

std::string Player::SaveAllConvoyStatus() const
{
	if (m_allConvoyStatus.empty()) { return {}; }
	ObjectHookEvent_OnSendMessage("SaveConvoyStatus", {});
	TextPacker packer;
	for (auto&[questTypeID, convoyStatus] : m_allConvoyStatus) {
		(packer << questTypeID << convoyStatus.instGuid.instGUID
			<< convoyStatus.status).PutDelimiter(';');
	}
	return packer.str();
}

void Player::LoadAllConvoyStatus(const char* data)
{
	TextUnpacker unpacker(data);
	while (!unpacker.IsEmpty()) {
		auto questTypeID = unpacker.Unpack<uint32>();
		auto& convoyStatus = m_allConvoyStatus[questTypeID];
		unpacker >> convoyStatus.instGuid.instGUID >> convoyStatus.status;
	}
}

void Player::RestoreAllConvoyStatus()
{
	auto itr = m_allConvoyStatus.begin();
	while (itr != m_allConvoyStatus.end()) {
		auto pQuestLog = m_pQuestStorage->GetQuestLogByEntry(itr->first);
		if (pQuestLog == NULL) {
			itr = m_allConvoyStatus.erase(itr);
		} else {
			auto& convoyStatus = itr++->second;
			if (CanRestoreConvoyStatus(convoyStatus)) {
				auto questScript = pQuestLog->
					GetQuestProto()->questScripts[(int)QuestWhenType::Accept];
				RunScriptFileById(m_pMapInstance->L, questScript.scriptID, this, pQuestLog,
					std::string_view(questScript.scriptArgs), std::string_view(convoyStatus.status));
			}
		}
	}
	if (!m_allConvoyStatus.empty()) {
		TryCreateObjectHook4ConvoyStatus();
	}
}

void Player::TryCreateObjectHook4ConvoyStatus()
{
	if (m_objHookKey4ConvoyStatus == 0) {
		static const std::string scriptFile = "scripts/Player/WatchQuestConvoy.lua";
		XRunScriptFile(m_pMapInstance->L, scriptFile, 1, this);
		m_objHookKey4ConvoyStatus = LuaFifoTopValue(m_pMapInstance->L).GetValue<uint32>();
	}
}

bool Player::CanRestoreConvoyStatus(const ConvoyStatus& convoyStatus) const
{
	auto instGuid = convoyStatus.instGuid;
	if (instGuid.MAPID != m_instGuid.MAPID || instGuid.TID != m_instGuid.TID) {
		return false;
	}
	TextUnpacker unpacker(convoyStatus.status.c_str());
	for (int i = 0; i < 2; ++i) {
		unpacker.Unpack4sv();
	}
	vector3f pos;
	unpacker >> pos.x >> pos.y >> pos.z;
	if (!m_pMapInstance->IsValidPosition(pos)) {
		return false;
	}
	return true;
}

void Player::SendPlayTips(uint32 stringID, uint32 lifeTime, const std::string_view& args)
{
	NetPacket pack(SMSG_PLAY_TIPS);
	(pack << stringID << lifeTime).Append(args.data(), args.size());
	SendPacket(pack);
}


void Player::ObjectHookEvent_OnPlayerChangeQuestStatus(QuestLog* pQuestLog, QuestWhenType type)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnPlayerChangeQuestStatus, "OnPlayerChangeQuestStatus", pQuestLog, type);
}

void Player::ObjectHookEvent_OnPlayerDelete()
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnPlayerDelete, "OnPlayerDelete");
}
